package javango.contrib.hibernate;

import com.google.inject.Inject;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javango.forms.fields.SelectField;
import javango.forms.widgets.ChoicesWidget;
import javango.forms.widgets.Widget;
import javango.forms.widgets.WidgetFactory;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;


public class ModelChoiceField extends SelectField {

	private final static Log log = LogFactory.getLog(ModelChoiceField.class);
	
	private Class model;
	private Criteria criteria;
	protected HibernateUtil hibernateUtil;

	public ModelChoiceField() {
		this(JavangoModule.injector.getInstance(WidgetFactory.class), JavangoModule.injector.getInstance(HibernateUtil.class));
	}
	
	public ModelChoiceField(Class modelClass) {
		this(JavangoModule.injector.getInstance(WidgetFactory.class), JavangoModule.injector.getInstance(HibernateUtil.class), modelClass);
	}
    
    @Inject
	public ModelChoiceField(WidgetFactory widgetFactory, HibernateUtil hibernateUtil) {
		super(widgetFactory);
		this.hibernateUtil = hibernateUtil;
	}
	
	// TODO Keep these constructors,  or just force the programmer to call setModel, setChoices themselves??
	public ModelChoiceField(WidgetFactory widgetFactory, HibernateUtil hibernateUtil, Class modelClass) {
		this(widgetFactory, hibernateUtil);
		setModel(modelClass);
	}
	
	public ModelChoiceField(WidgetFactory widgetFactory, HibernateUtil hibernateUtil, Class modelClass, List<?> choiceList) {
		this(widgetFactory, hibernateUtil);
		this.model = modelClass;			
		setChoices(choiceList);
	}
	
	public ModelChoiceField setModel(Class modelClass) {
		this.model = modelClass;
		return this;
	}
	
	public ModelChoiceField setCriteria(Criteria criteria) {
		this.criteria = criteria;
		return this;
	}
	
	public ModelChoiceField setChoices(List<?> choiceList) {
		if (model == null) {
			// TODO Iteratr the list looking for the first non-null entry,  nulls shoudl be treaded as a blank entry.
			// set model class to the class of the first choice.
			if (choiceList != null && choiceList.size() > 0) {
				Class first = choiceList.get(0).getClass(); 
				this.model = first == null ? null : first;
			} else { // no choices
				return this;
			}
			if (model == null) {
				log.error("Unable to set choices without a modelClass");
				return null;
			}
		}
		
		try {
			Map<String, Object> choices = new LinkedHashMap<String, Object>();

			ModelDao dao = new ModelDao(hibernateUtil, model);
			for (Object choice : choiceList) {
				choices.put(dao.getPk(choice).toString(), choice.toString());
			}
	
			setChoices(choices);
		} catch (DaoException e) {
			log.error(e,e);
		}
		return this;
	}
	
	@Override
	public long getChoiceCount() {
		if (super.getChoices() != null) {
			return super.getChoiceCount();			
		}
		try {
			// TODO add the query logic!!
			
			ModelDao dao = new ModelDao(hibernateUtil, model);
		
			return dao.count();
		} catch (DaoException e) {
			log.error(e,e);
			return 0;
		}
	}

	@Override
	public Map<String, Object> getChoices() {
		if (super.getChoices() != null) {
			return super.getChoices();
		}
		Map<String, Object> choices = new LinkedHashMap<String, Object>();
		try {
			ModelDao dao = new ModelDao(hibernateUtil, model);
			
			if (criteria != null) {
				for (Object choice : criteria.list()) {
					choices.put(dao.getPk(choice).toString(), choice.toString());
				}				
			} else {				
				for (Object choice : dao.filter().list()) {
					choices.put(dao.getPk(choice).toString(), choice.toString());
				}
			}
	
		} catch (DaoException e) {
			log.error(e,e);
		}
		super.setChoices(choices);
		return super.getChoices();
	}

	@Override
	protected Object clean(String value, Map<String, String> errors) {
		if (StringUtils.isEmpty(value)) {
			if (isRequired()) { 
				errors.put(getName(), "Required");
			} else if (isAllowNull()) {
				return null;
			}
		}
		return value;
	}
	
	@Override
	public Object clean(String[] value, Map<String, String> errors) {
		log.debug("Starting clean for " + getName());
		String pk;
		
		if (value == null || value.length == 0) {
			pk = (String)clean("", errors);
		} else {
			pk = (String)clean(value[0], errors);
		}

		
		log.debug("Got pk=" + pk);
		if (!errors.isEmpty()) return null;

		try {
			if (pk == null) {
				return null;
			}
			Object object = new ModelDao(hibernateUtil, model).get(pk);
			if (object == null) {
				errors.put(this.name, "Select a valid choice.  That choice is not one of the available choices.");
			}
			return object;
		} catch (DaoException e) {
			log.error(e,e);
			errors.put(this.getName(), e.toString());
		}
		return null;
	}

	@Override
	public Widget getWidget() {
		Widget w = super.getWidget();
		if (w instanceof ChoicesWidget) {
			((ChoicesWidget) w).setField(this);			
		}
		return w;
	}

	public Class getModel() {
		return model;
	}
	

	
}
